CoderHann

自动布局SnapKit VS VFL

前言

之前我们画UI都是使用frame计算对控件布局的,随着iPhone尺寸的多样化,苹果自己也推出了各种各样的布局方式:AutoresizingaotulayoutVFL。我们开发也逐渐的转向自动布局,aotulayout在xib上布局还是用的挺好,但是对于不用xib的同学来说只能选择VFL进行代码布局了,但是了解VFL的同学都知道其格式复杂、问题不好找,关键不是所有的同学都会VFL你写的代码别人也不好维护了。后来有个第三方框架Masonry出现了感觉拯救了使用代码自动布局的同学们。由于本章项目基于Swift语言的,所以我们使用的是SnapKit框架(Masonry的Swift版本)。本章主要通过实例演练一个UI布局来对比下代码编程中VFL和SnapKit到底哪个用的爽,并不对其进行详细讲解,需要了解他们用法的童鞋可以到github上看文档或者去网上搜索自己想要的资料。

demo演示

我们先看下demo的最终效果图:

该demo演示布局参考了新浪微博cell的样式,主要包括头像、昵名、时间、来源、更多、正文、转发正文、图片列表、工具栏等控件。在实战之前最好去微博看下它的布局,然后再看本篇文章代码就会更加清晰了。

横屏效果图:

竖屏更多内容效果图:

控件的创建及初始化:
为了节省本文空间,就使用VFL控制器中的控件创建代码(VFL和SnapKit创建代码一致的除标题)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class VFLViewController: UIViewController {
// subviews
var whiteView: UIView?;
// whiteSubviews
var iconBtn: UIButton?;
var nameLabel: UILabel?;
var timeLabel: UILabel?;
var sourceLabel: UILabel?;
var arrowBtn: UIButton?;
var firstLabel: UILabel?;
var secondLabel: UILabel?;
var imagesView: UIView?;
var actionView: UIView?;
// imagesSubviews
var imageOne: UIImageView?;
var imageTwo: UIImageView?;
var imageThree: UIImageView?;
// actionSubviews
var btnOne: UIButton?;
var btnTwo: UIButton?;
var btnThree: UIButton?;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "VFL";
self.view.backgroundColor = UIColor.white;
//init
self.setup();
//layout VFL
self.layout();
self.layoutWhiteView();
self.layoutImagesView();
self.layoutActioinView();
}
func setup() {
self.view.backgroundColor = UIColor.gray;
whiteView = UIView.init();
whiteView?.backgroundColor = UIColor.white;
self.view.addSubview(whiteView!);
//
iconBtn = UIButton.init();
iconBtn?.backgroundColor = UIColor.orange;
iconBtn?.layer.cornerRadius = 6;
iconBtn?.clipsToBounds = true;
whiteView?.addSubview(iconBtn!);
nameLabel = UILabel.init();
nameLabel?.backgroundColor = UIColor.green;
whiteView?.addSubview(nameLabel!);
timeLabel = UILabel.init();
timeLabel?.backgroundColor = UIColor.gray;
whiteView?.addSubview(timeLabel!);
sourceLabel = UILabel.init();
sourceLabel?.backgroundColor = UIColor.gray;
whiteView?.addSubview(sourceLabel!);
arrowBtn = UIButton.init();
arrowBtn?.backgroundColor = UIColor.brown;
whiteView?.addSubview(arrowBtn!);
firstLabel = UILabel.init();
firstLabel?.backgroundColor = UIColor.lightGray;
firstLabel?.numberOfLines = 0;
firstLabel?.text = "firstLabel,firstLabel,firstLabel,firstLabel,firstLabel,firstLabel";
whiteView?.addSubview(firstLabel!);
secondLabel = UILabel.init();
secondLabel?.backgroundColor = UIColor.lightGray;
secondLabel?.numberOfLines = 0;
secondLabel?.text = "secondLabel,secondLabel,secondLabel,secondLabel,secondLabel";
whiteView?.addSubview(secondLabel!);
imagesView = UIView.init();
imagesView?.backgroundColor = UIColor.purple;
whiteView?.addSubview(imagesView!);
actionView = UIView.init();
actionView?.backgroundColor = UIColor.gray;
whiteView?.addSubview(actionView!);
//images
imageOne = UIImageView.init();
imageOne?.backgroundColor = UIColor.orange;
imageOne?.layer.cornerRadius = 3;
imageOne?.clipsToBounds = true;
imagesView?.addSubview(imageOne!);
imageTwo = UIImageView.init();
imageTwo?.backgroundColor = UIColor.orange;
imageTwo?.layer.cornerRadius = 3;
imageTwo?.clipsToBounds = true;
imagesView?.addSubview(imageTwo!);
imageThree = UIImageView.init();
imageThree?.backgroundColor = UIColor.orange;
imageThree?.layer.cornerRadius = 3;
imageThree?.clipsToBounds = true;
imagesView?.addSubview(imageThree!);
//actionView
btnOne = UIButton.init();
btnOne?.backgroundColor = UIColor.blue;
btnOne?.alpha = 0.4;
actionView?.addSubview(btnOne!);
btnTwo = UIButton.init();
btnTwo?.backgroundColor = UIColor.blue;
btnTwo?.alpha = 0.4;
actionView?.addSubview(btnTwo!);
btnThree = UIButton.init();
btnThree?.backgroundColor = UIColor.blue;
btnThree?.alpha = 0.4;
actionView?.addSubview(btnThree!);
}
// 增加firstLabel,secondLabel文字内容
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
firstLabel?.text = firstLabel?.text?.appending(",firstLabel");
secondLabel?.text = secondLabel?.text?.appending(",secondLabel");
}
}

VFL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
func layout() {
whiteView?.translatesAutoresizingMaskIntoConstraints = false;
let views: [String: Any] = ["whiteView": whiteView!];
let metrics: [String: Any] = ["marginV": 10, "marginH": 10];
var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-20-[whiteView]", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views);
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-marginH-[whiteView]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
self.view.addConstraints(constraints);
}
func layoutWhiteView() {
// forbid autoresizing
iconBtn?.translatesAutoresizingMaskIntoConstraints = false;
nameLabel?.translatesAutoresizingMaskIntoConstraints = false;
timeLabel?.translatesAutoresizingMaskIntoConstraints = false;
sourceLabel?.translatesAutoresizingMaskIntoConstraints = false;
arrowBtn?.translatesAutoresizingMaskIntoConstraints = false;
firstLabel?.translatesAutoresizingMaskIntoConstraints = false;
secondLabel?.translatesAutoresizingMaskIntoConstraints = false;
imagesView?.translatesAutoresizingMaskIntoConstraints = false;
actionView?.translatesAutoresizingMaskIntoConstraints = false;
let views: [String: Any] = ["iconBtn": iconBtn!,"nameLabel": nameLabel!,"timeLabel": timeLabel!,"sourceLabel": sourceLabel!,"arrowBtn": arrowBtn!,"firstLabel": firstLabel!,"secondLabel": secondLabel!,"imagesView": imagesView!,"actionView": actionView!];
let metrics: [String: Any] = ["marginH": 10, "marginV": 10, "paddingT1": 5, "paddingT2": 10,"iconEdge": 50, "imageV": 150, "actionV": 35];
var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "V:|-marginV-[iconBtn(iconEdge)]-paddingT2-[firstLabel]-paddingT2-[secondLabel]-paddingT1-[imagesView(imageV)]-paddingT1-[actionView(actionV)]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views);
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-marginV-[nameLabel(25)]->=0-[timeLabel(20)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:[nameLabel]->=0-[sourceLabel(20)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(NSLayoutConstraint.init(item: timeLabel!, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: iconBtn!, attribute: NSLayoutAttribute.bottom, multiplier: 1.0, constant: 0.0));
constraints.append(NSLayoutConstraint.init(item: sourceLabel!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: timeLabel!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: arrowBtn!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: nameLabel!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: arrowBtn!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: arrowBtn!, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0.0))
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-marginH-[iconBtn(iconEdge)]-paddingT1-[nameLabel(200)]->=0-[arrowBtn(20)]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:[iconBtn]-paddingT1-[timeLabel(60)]-paddingT1-[sourceLabel(100)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat:"H:|-marginH-[firstLabel]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat:"H:|-marginH-[secondLabel]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat:"H:|-marginH-[imagesView]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat:"H:|-marginH-[actionView]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views));
whiteView?.addConstraints(constraints);
}
func layoutImagesView() {
imageOne?.translatesAutoresizingMaskIntoConstraints = false;
imageTwo?.translatesAutoresizingMaskIntoConstraints = false;
imageThree?.translatesAutoresizingMaskIntoConstraints = false;
let views: [String: Any] = ["imageOne": imageOne!,"imageTwo": imageTwo!,"imageThree": imageThree!];
let metrics: [String: Any] = ["marginH": 10, "marginV": 20, "padding": 5];
var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "H:|-marginH-[imageOne]-padding-[imageTwo]-padding-[imageThree]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views);
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat:"V:|-marginV-[imageOne]-marginV-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views))
constraints.append(NSLayoutConstraint.init(item: imageTwo!, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: imageTwo!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: imageTwo!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: imageThree!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: imageThree!, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: imageThree!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: imageOne!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
imagesView?.addConstraints(constraints);
}
func layoutActioinView() {
btnOne?.translatesAutoresizingMaskIntoConstraints = false;
btnTwo?.translatesAutoresizingMaskIntoConstraints = false;
btnThree?.translatesAutoresizingMaskIntoConstraints = false;
let views: [String: Any] = ["btnOne": btnOne!,"btnTwo": btnTwo!,"btnThree": btnThree!];
let metrics: [String: Any] = ["marginH": 0, "padding": 1];
var constraints: [NSLayoutConstraint] = NSLayoutConstraint.constraints(withVisualFormat: "H:|-marginH-[btnOne]-padding-[btnTwo]-padding-[btnThree]-marginH-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views);
constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|[btnThree]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views))
constraints.append(NSLayoutConstraint.init(item: btnTwo!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: btnThree!, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.height, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: btnTwo!, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: btnThree!, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.width, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: btnTwo!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
constraints.append(NSLayoutConstraint.init(item: btnThree!, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: btnOne!, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0.0))
actionView?.addConstraints(constraints);
}

SnapKit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
func layout() {
whiteView?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.right.equalTo(-10);
maker.top.equalTo(20);
});
}
func layoutWhiteView() {
iconBtn?.snp.makeConstraints({ (maker) in
maker.width.height.equalTo(50);
maker.top.left.equalTo(10);
});
nameLabel?.snp.makeConstraints({ (maker) in
maker.height.equalTo(25);
maker.width.equalTo(200);
maker.top.equalTo(10);
maker.left.equalTo((iconBtn?.snp.right)!).offset(5);
});
timeLabel?.snp.makeConstraints({ (maker) in
maker.width.equalTo(60);
maker.height.equalTo(20);
maker.left.equalTo((nameLabel?.snp.left)!);
maker.bottom.equalTo((iconBtn?.snp.bottom)!);
})
sourceLabel?.snp.makeConstraints({ (maker) in
maker.width.equalTo(100);
maker.height.equalTo(20);
maker.top.equalTo((timeLabel?.snp.top)!);
maker.left.equalTo((timeLabel?.snp.right)!).offset(5);
});
arrowBtn?.snp.makeConstraints({ (maker) in
maker.width.height.equalTo(20);
maker.top.equalTo((nameLabel?.snp.top)!);
maker.right.equalTo(-10);
})
firstLabel?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.right.equalTo(-10);
maker.top.equalTo((iconBtn?.snp.bottom)!).offset(10);
})
secondLabel?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.right.equalTo(-10);
maker.top.equalTo((firstLabel?.snp.bottom)!).offset(10);
})
imagesView?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.right.equalTo(-10);
maker.height.equalTo(150);
maker.top.equalTo((secondLabel?.snp.bottom)!).offset(5);
})
actionView?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.right.equalTo(-10);
maker.height.equalTo(35);
maker.top.equalTo((imagesView?.snp.bottom)!).offset(5);
maker.bottom.equalTo((whiteView?.snp.bottom)!).offset(-10);
})
}
func layoutImagesView() {
imageOne?.snp.makeConstraints({ (maker) in
maker.left.equalTo(10);
maker.top.equalTo(20);
maker.bottom.equalTo(-20);
maker.width.greaterThanOrEqualTo(0);
})
imageTwo?.snp.makeConstraints({ (maker) in
maker.top.bottom.width.equalTo(imageOne!);
maker.left.equalTo((imageOne?.snp.right)!).offset(5);
})
imageThree?.snp.makeConstraints({ (maker) in
maker.top.bottom.width.equalTo(imageOne!);
maker.left.equalTo((imageTwo?.snp.right)!).offset(5);
maker.right.equalTo(-10);
})
}
func layoutActioinView() {
btnOne?.snp.makeConstraints({ (maker) in
maker.left.top.bottom.equalTo(0);
maker.width.greaterThanOrEqualTo(0);
})
btnTwo?.snp.makeConstraints({ (maker) in
maker.top.bottom.width.equalTo(btnOne!);
maker.left.equalTo((btnOne?.snp.right)!).offset(1);
})
btnThree?.snp.makeConstraints({ (maker) in
maker.top.bottom.width.equalTo(btnOne!);
maker.left.equalTo((btnTwo?.snp.right)!).offset(1);
maker.right.equalTo(0);
})
}

小结

如果你自己将上面的demo自己敲一遍的话就会发现SnapKit比VFL好用了,SnapKit主要体现在条理清晰,不会像VFL那样一个空间的布局可能存在多个VFL语句中。如果VFL中出现了一个bug我觉得你要找好久,而使用SnapKit则方便维护与更新。是不是有想学习SnapKit的冲动了,SnapKit还是很好用的把上面的demo敲一遍基本的布局难不倒你了!